# -*- coding: utf-8 -*-

# tests/test_reference.py
# Part of ‘manpage’, a Python library for making Unix manual documents.
#
# Copyright © 2015–2016 Ben Finney <ben+python@benfinney.id.au>
#
# This is free software: see the grant of license at end of this file.

""" Unit tests for references within manual page documents. """

import sys
import os
import os.path
import operator
import textwrap
import unittest
import unittest.mock

__package__ = os.path.basename(os.path.dirname(os.path.abspath(__file__)))
__import__(__package__)
sys.path.insert(1, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import manpage.document


def setup_reference_instance(testcase, test_class=None, test_args=None):
    """ Set up a `Reference` instance. """
    if test_class is None:
        test_class = testcase.reference_class
    if test_args is None:
        test_args = testcase.test_args
    testcase.test_reference_instance = test_class(**test_args)


class Reference_TestCase(unittest.TestCase):
    """ Test cases for class `Reference`. """

    reference_class = manpage.document.Reference

    scenarios = [
            ('simple', {
                'test_args': {},
                'expected_class': reference_class,
                }),
            ]

    def setUp(self):
        """ Set up test fixtures. """
        super().setUp()

        self.scenarios_by_name = dict(self.scenarios)

    def test_instantiate(self):
        """ An instance should be created. """
        for (scenario_name, scenario) in self.scenarios:
            with self.subTest(scenario=scenario_name):
                setup_reference_instance(self, test_args=scenario['test_args'])
                self.assertIsInstance(
                        self.test_reference_instance,
                        self.reference_class)

    def test_as_markup_not_implemented(self):
        """ Method `as_markup` should raise NotImplementedError. """
        scenario = self.scenarios_by_name['simple']
        instance = self.reference_class(**scenario['test_args'])
        with self.assertRaises(NotImplementedError):
            instance.as_markup()

    def test_comparison_tuple_not_implemented(self):
        """ Property `_comparison_tuple` should raise NotImplementedError. """
        scenario = self.scenarios_by_name['simple']
        instance = self.reference_class(**scenario['test_args'])
        with self.assertRaises(NotImplementedError):
            instance._comparison_tuple


class Reference_comparison_TestCaseMixIn:
    """ Mix-in to add comparison test cases for a `Reference` class. """

    def test_comparisons_as_expected(self):
        """ Comparisons should be as expected. """
        for (scenario_name, scenario) in self.scenarios:
            with self.subTest(scenario=scenario_name):
                for (operator_name, operator_func) in (
                        (name, getattr(operator, name))
                        for name in scenario['expected_true']):
                    with self.subTest(operator=operator_name):
                        result = operator_func(
                                scenario['instance_this'],
                                scenario['instance_other'])
                        self.assertTrue(result)
                for (operator_name, operator_func) in (
                        (name, getattr(operator, name))
                        for name in scenario['expected_false']):
                    with self.subTest(operator=operator_name):
                        result = operator_func(
                                scenario['instance_this'],
                                scenario['instance_other'])
                        self.assertFalse(result)


class DocumentReference_TestCase(unittest.TestCase):
    """ Test cases for class `DocumentReference`. """

    reference_class = manpage.document.DocumentReference

    def setUp(self):
        """ Set up test fixtures. """
        super().setUp()

        self.test_args = {
                'name': "lorem",
                'section': "99",
                }

        setup_reference_instance(self)

    def test_instantiate(self):
        """ An instance should be created. """
        self.assertIsInstance(
                self.test_reference_instance, self.reference_class)

    def test_has_specified_name(self):
        """ Should have `name` value specified. """
        self.assertEqual(
                self.test_reference_instance.name, self.test_args['name'])

    def test_has_specified_section(self):
        """ Should have `section` value specified. """
        self.assertEqual(
                self.test_reference_instance.section,
                self.test_args['section'])

    def test_text_representation_as_expected(self):
        """ Should have expected text representation. """
        expected_text = "{0.name} ({0.section})".format(
                self.test_reference_instance)
        self.assertEqual(
                str(self.test_reference_instance), expected_text)

    def test_programmer_representation_as_expected(self):
        """ Should have expected programmer representation. """
        class_name = self.reference_class.__name__
        expected_text = "{class_name}({name!r}, {section!r})".format(
                class_name=class_name,
                name=self.test_reference_instance.name,
                section=self.test_reference_instance.section)
        self.assertEqual(
                repr(self.test_reference_instance), expected_text)


class DocumentReference_as_markup_TestCase(unittest.TestCase):
    """ Test cases for method `DocumentReference.as_markup`. """

    reference_class = manpage.document.DocumentReference

    scenarios = [
            ('simple', {
                'test_args': {
                    'name': "lorem",
                    'section': "99",
                    },
                'expected_result': textwrap.dedent("""\
                        .BR lorem (99)
                        """),
                }),
            ('name-hyphenated', {
                'test_args': {
                    'name': "lorem-ipsum",
                    'section': "99",
                    },
                'expected_result': textwrap.dedent("""\
                        .BR lorem\\-ipsum (99)
                        """),
                }),
            ]

    def test_returns_expected_result(self):
        """ Should return expected markup for inputs. """
        for (scenario_name, scenario) in self.scenarios:
            with self.subTest(scenario=scenario_name):
                setup_reference_instance(self, test_args=scenario['test_args'])
                expected_markup = textwrap.dedent("""\
                        .BR {0[name]} ({0[section]})
                        """).format(scenario['test_args'])
                result = self.test_reference_instance.as_markup()
                self.assertEqual(result, expected_markup)


class DocumentReference_from_text_TestCase(unittest.TestCase):
    """ Test cases for method `DocumentReference.from_text`. """

    error_class = manpage.document.DocumentReference.ReferenceFormatError

    scenarios = [
            ('simple', {
                'test_text': "lorem(99)",
                'expected_instance': manpage.document.DocumentReference(
                    "lorem", "99"),
                }),
            ('name-hyphenated', {
                'test_text': "lorem-ipsum(99)",
                'expected_instance': manpage.document.DocumentReference(
                    "lorem-ipsum", "99"),
                }),
            ('section-contains-nondigit', {
                'test_text': "lorem-ipsum(99xyz)",
                'expected_instance': manpage.document.DocumentReference(
                    "lorem-ipsum", "99xyz"),
                }),
            ('empty', {
                'test_text': "",
                'expected_error': error_class,
                }),
            ('name-only', {
                'test_text': "lorem-ipsum",
                'expected_error': error_class,
                }),
            ('section-empty', {
                'test_text': "lorem-ipsum()",
                'expected_error': error_class,
                }),
            ('section-starts-nondigit', {
                'test_text': "lorem-ipsum(xyz99)",
                'expected_error': error_class,
                }),
            ]

    def setUp(self):
        """ Set up test fixtures. """
        super().setUp()

        self.reference_class = manpage.document.DocumentReference

    def test_returns_expected_result(self):
        """ Should return expected instance for inputs. """
        for (scenario_name, scenario) in (
                (scenario_name, scenario)
                for (scenario_name, scenario) in self.scenarios
                if 'expected_instance' in scenario):
            with self.subTest(scenario=scenario_name):
                instance = self.reference_class.from_text(
                        scenario['test_text'])
                self.assertEqual(instance, scenario['expected_instance'])

    def test_raises_expected_error(self):
        """ Should raise expected error for inputs. """
        for (scenario_name, scenario) in (
                (scenario_name, scenario)
                for (scenario_name, scenario) in self.scenarios
                if 'expected_error' in scenario):
            with self.subTest(scenario=scenario_name):
                with self.assertRaises(scenario['expected_error']):
                    __ = self.reference_class.from_text(
                            scenario['test_text'])


class DocumentReference_comparison_TestCase(
        unittest.TestCase, Reference_comparison_TestCaseMixIn):
    """ Test cases for `DocumentReference` comparison methods. """

    reference_class = manpage.document.DocumentReference

    scenarios = [
            ('type-same,equal', {
                'instance_this': reference_class("lorem", "5"),
                'instance_other': reference_class("lorem", "5"),
                'expected_true': ['eq', 'le', 'ge'],
                'expected_false': ['ne', 'lt', 'gt'],
                }),
            ('type-same,name-less', {
                'instance_this': reference_class("lorem", "5"),
                'instance_other': reference_class("nullam", "5"),
                'expected_true': ['ne', 'le', 'lt'],
                'expected_false': ['eq', 'ge', 'gt'],
                }),
            ('type-same,name-greater', {
                'instance_this': reference_class("lorem", "5"),
                'instance_other': reference_class("dolor", "5"),
                'expected_true': ['ne', 'ge', 'gt'],
                'expected_false': ['eq', 'le', 'lt'],
                }),
            ('type-same,section-less', {
                'instance_this': reference_class("lorem", "5"),
                'instance_other': reference_class("lorem", "8"),
                'expected_true': ['ne', 'le', 'lt'],
                'expected_false': ['eq', 'ge', 'gt'],
                }),
            ('type-same,section-greater', {
                'instance_this': reference_class("lorem", "5"),
                'instance_other': reference_class("lorem", "1"),
                'expected_true': ['ne', 'ge', 'gt'],
                'expected_false': ['eq', 'le', 'lt'],
                }),
            ('type-different,other-external', {
                'instance_this': reference_class("lorem", "5"),
                'instance_other': manpage.document.ExternalReference(
                        "Alpha Beta"),
                'expected_true': ['ne', 'le', 'lt'],
                'expected_false': ['eq', 'ge', 'gt'],
                }),
            ('type-different,other-not-reference', {
                'instance_this': reference_class("lorem", "5"),
                'instance_other': (5, 5),
                'expected_true': ['ne', 'ge', 'gt'],
                'expected_false': ['eq', 'le', 'lt'],
                }),
            ]


class ExternalReference_TestCase(unittest.TestCase):
    """ Test cases for class `ExternalReference`. """

    reference_class = manpage.document.ExternalReference

    scenarios = [
            ('simple', {
                'test_args': {
                    'title': "Lorem Ipsum",
                    'url': "spam://example.org/beans/",
                    },
                'expected_text_representation': (
                    "Lorem Ipsum <URL:spam://example.org/beans/>"),
                'expected_programmer_representation': (
                    "ExternalReference"
                    "('Lorem Ipsum', 'spam://example.org/beans/')"),
                }),
            ('url-missing', {
                'test_args': {
                    'title': "Lorem Ipsum",
                    'url': None,
                    },
                'expected_text_representation': "Lorem Ipsum",
                'expected_programmer_representation': (
                    "ExternalReference('Lorem Ipsum', None)"),
                }),
            ]

    def setUp(self):
        """ Set up test fixtures. """
        super().setUp()

        simple_scenario = dict(self.scenarios)['simple']
        self.test_args = simple_scenario['test_args']

        setup_reference_instance(self)

    def test_instantiate(self):
        """ An instance should be created. """
        self.assertIsInstance(
                self.test_reference_instance, self.reference_class)

    def test_has_specified_title(self):
        """ Should have `title` value specified. """
        self.assertEqual(
                self.test_reference_instance.title, self.test_args['title'])

    def test_has_specified_url(self):
        """ Should have `url` value specified. """
        self.assertEqual(
                self.test_reference_instance.url, self.test_args['url'])

    def test_text_representation_as_expected(self):
        """ Should have expected text representation. """
        for (scenario_name, scenario) in self.scenarios:
            with self.subTest(scenario=scenario_name):
                instance = self.reference_class(
                        **scenario['test_args'])
                self.assertEqual(
                        str(instance),
                        scenario['expected_text_representation'])

    def test_programmer_representation_as_expected(self):
        """ Should have expected programmer representation. """
        for (scenario_name, scenario) in self.scenarios:
            with self.subTest(scenario=scenario_name):
                instance = self.reference_class(
                        **scenario['test_args'])
                self.assertEqual(
                        repr(instance),
                        scenario['expected_programmer_representation'])


class ExternalReference_as_markup_TestCase(unittest.TestCase):
    """ Test cases for method `DocumentReference.as_markup`. """

    reference_class = manpage.document.ExternalReference

    scenarios = [
            ('simple', {
                'test_args': {
                    'title': "Lorem Ipsum",
                    'url': "spam://example.org/beans/",
                    },
                'expected_result': textwrap.dedent("""\
                    .UR spam://example.org/beans/
                    Lorem Ipsum
                    .UE
                    """),
                }),
            ('url-missing', {
                'test_args': {
                    'title': "Lorem Ipsum",
                    'url': None,
                    },
                'expected_result': textwrap.dedent("""\
                    Lorem Ipsum
                    """),
                }),
            ]

    def test_returns_expected_result(self):
        """ Should return expected markup for inputs. """
        for (scenario_name, scenario) in self.scenarios:
            with self.subTest(scenario=scenario_name):
                setup_reference_instance(self, test_args=scenario['test_args'])
                result = self.test_reference_instance.as_markup()
                self.assertEqual(result, scenario['expected_result'])


class ExternalReference_comparison_TestCase(
        unittest.TestCase, Reference_comparison_TestCaseMixIn):
    """ Test cases for `ExternalReference` comparison methods. """

    reference_class = manpage.document.ExternalReference

    scenarios = [
            ('type-same,equal', {
                'instance_this': reference_class("Lorem", "spam"),
                'instance_other': reference_class("Lorem", "spam"),
                'expected_true': ['eq', 'le', 'ge'],
                'expected_false': ['ne', 'lt', 'gt'],
                }),
            ('type-same,title-less', {
                'instance_this': reference_class("Lorem", "spam"),
                'instance_other': reference_class("Nullam", "spam"),
                'expected_true': ['ne', 'le', 'lt'],
                'expected_false': ['eq', 'ge', 'gt'],
                }),
            ('type-same,title-greater', {
                'instance_this': reference_class("Lorem", "spam"),
                'instance_other': reference_class("Dolor", "spam"),
                'expected_true': ['ne', 'ge', 'gt'],
                'expected_false': ['eq', 'le', 'lt'],
                }),
            ('type-same,url-less', {
                'instance_this': reference_class("Lorem", "spam"),
                'instance_other': reference_class("Lorem", "xyzzy"),
                'expected_true': ['ne', 'le', 'lt'],
                'expected_false': ['eq', 'ge', 'gt'],
                }),
            ('type-same,section-greater', {
                'instance_this': reference_class("Lorem", "spam"),
                'instance_other': reference_class("Lorem", "beans"),
                'expected_true': ['ne', 'ge', 'gt'],
                'expected_false': ['eq', 'le', 'lt'],
                }),
            ('type-different,other-document', {
                'instance_this': reference_class("Lorem", "spam"),
                'instance_other': manpage.document.DocumentReference.from_text(
                        "nullam(8)"),
                'expected_true': ['ne', 'ge', 'gt'],
                'expected_false': ['eq', 'le', 'lt'],
                }),
            ('type-different,other-not-reference', {
                'instance_this': reference_class("Lorem", "spam"),
                'instance_other': (5, 5),
                'expected_true': ['ne', 'ge', 'gt'],
                'expected_false': ['eq', 'le', 'lt'],
                }),
            ]


# This is free software: you may copy, modify, and/or distribute this work
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; version 3 of that license or any later version.
#
# No warranty expressed or implied. See the file ‘LICENSE.GPL-3’ for details,
# or view it online at <URL:https://www.gnu.org/licenses/gpl-3.0.html>.


# Local variables:
# coding: utf-8
# mode: python
# End:
# vim: fileencoding=utf-8 filetype=python :
